package adminToken

import (
	"bytes"
	"duanju-mq/pkg/app"
	"duanju-mq/pkg/config"
	"duanju-mq/pkg/crypto/aes"
	"duanju-mq/pkg/json"
	"duanju-mq/pkg/util/rand"
	"errors"
	"net/http"
	"strconv"
	"strings"
	"sync"
	"time"
)

var (
	fTokenExpire     = config.Int("token.expire", 3600, "token过期时间 秒")
	fTokenCookieName = config.String("token.cookie", "cookie", "token cookie name")
	fTokenPath       = config.String("token.path", "/", "path root")
	fTokenSecure     = config.Bool("token.secure", false, "token secure")
	Tokens           = map[int]*TokenData{}
	tokenlock        = sync.Mutex{}
)

type TokenData struct {
	Uid        int
	TokenKey   string
	ExpireTime int64
}

func init() {
	app.Async(func() {
		for {
			time.Sleep(time.Second * 10)
			delExpireToken()
		}
	})
}

func CreateToken(uid int, w http.ResponseWriter, r *http.Request) (string, error) {
	str, err := generateToken(uid)
	if err != nil {
		return "", err
	}
	sesVal := strconv.Itoa(uid) + "-" + str
	http.SetCookie(w, &http.Cookie{
		Name:     "app_" + *fTokenCookieName,
		Value:    sesVal,
		HttpOnly: true,
		Path:     *fTokenPath,
		Secure:   *fTokenSecure,
	})

	return sesVal, nil
}
func DistoryToken(uid int, w http.ResponseWriter, r *http.Request) {
	tokenlock.Lock()
	defer tokenlock.Unlock()
	if _, ok := Tokens[uid]; ok {
		delete(Tokens, uid)
	}
	http.SetCookie(w, &http.Cookie{Name: "app_" + *fTokenCookieName, MaxAge: -1, Path: "/"})
}

func delExpireToken() {
	tokenlock.Lock()
	defer tokenlock.Unlock()
	now := time.Now().Unix()
	del := []*TokenData{}
	for _, sess := range Tokens {
		if now > sess.ExpireTime {
			del = append(del, sess)
		}
	}
	for _, sess := range del {
		delete(Tokens, sess.Uid)
	}
}

func generateToken(uid int) (string, error) {
	tk := &TokenData{Uid: uid}
	b, err := json.Marshal(tk)
	if err != nil {
		return "", err
	}
	skey := rand.RandStr(32)
	if err := setSessionKey(uid, skey); err != nil {
		return "", err
	}
	str, err := aes.EncryptToString([]byte(skey), b)
	return str, err
}

func getSessionKey(uid int) string {
	tokenlock.Lock()
	defer tokenlock.Unlock()
	sess, ok := Tokens[uid]
	if !ok {
		return ""
	}
	sess.ExpireTime = time.Now().Unix() + int64(*fTokenExpire)
	return sess.TokenKey
}

func setSessionKey(uid int, skey string) error {
	tokenlock.Lock()
	defer tokenlock.Unlock()
	et := time.Now().Unix() + int64(*fTokenExpire)
	if sess, ok := Tokens[uid]; ok {
		sess.TokenKey = skey
		sess.ExpireTime = et
	} else {
		Tokens[uid] = &TokenData{
			Uid:        uid,
			TokenKey:   skey,
			ExpireTime: et,
		}
	}
	return nil
}

func jsonEncode(v any) (string, error) {
	buf := new(bytes.Buffer)
	enc := json.NewEncoder(buf)
	enc.SetEscapeHTML(false)
	err := enc.Encode(v)
	return strings.TrimSpace(buf.String()), err
}

func GetLoginUid(r *http.Request) (int, error) {
	tk, err := getLoginToken(r)
	if err != nil {
		return 0, err
	}
	return tk.Uid, nil
}

func getLoginToken(r *http.Request) (*TokenData, error) {
	token := ""
	if ck, err := r.Cookie("app_" + *fTokenCookieName); err == nil {
		token = ck.Value
	}
	if token == "" {
		token = r.PostForm.Get("_t")
		if token == "" {
			token = r.URL.Query().Get("_t")
		}
	}
	if token == "" {
		return nil, errors.New("未找到cookie，请重新登录!")
	}
	ss := strings.SplitN(token, "-", 2)
	uid, err := strconv.Atoi(ss[0])
	if err != nil {
		return nil, errors.New("无效的cookie!")
	}
	skey := getSessionKey(uid)
	if skey == "" {
		return nil, errors.New("验证失败，请重新登录")
	}
	b, err := aes.DecryptString([]byte(skey), ss[1])
	if err != nil {
		return nil, errors.New("在其他客户度端登录!")
	}
	tk := &TokenData{}
	if err := json.Unmarshal(b, &tk); err != nil {
		return nil, err
	}
	if uid != tk.Uid {
		return nil, errors.New("cookie失效，请重新登录!")
	}
	return tk, nil
}
